//: exceptions/DynamicFields.java
// A Class that dynamically adds fields to itself.
// Demonstrates exception chaining.
package exceptions;

import static net.mindview.util.Print.*;

class DynamicFieldsException extends Exception
{
}

public class DynamicFields
{
    private Object[][] fields;
    
    public DynamicFields(int initialSize)
    {
        fields = new Object[initialSize][2];
        for (int i = 0; i < initialSize; i++)
            fields[i] = new Object[] {null, null};
    }
    
    public String toString()
    {
        StringBuilder result = new StringBuilder();
        for (Object[] obj : fields)
        {
            result.append(obj[0]);
            result.append(": ");
            result.append(obj[1]);
            result.append("\n");
        }
        return result.toString();
    }
    
    private int hasField(String id)
    {
        for (int i = 0; i < fields.length; i++)
            if (id.equals(fields[i][0]))
                return i;
        return -1;
    }
    
    private int getFieldNumber(String id)
        throws NoSuchFieldException
    {
        int fieldNum = hasField(id);
        if (fieldNum == -1)
            throw new NoSuchFieldException();
        return fieldNum;
    }
    
    private int makeField(String id)
    {
        for (int i = 0; i < fields.length; i++)
            if (fields[i][0] == null)
            {
                fields[i][0] = id;
                return i;
            }
        // No empty fields. Add one:
        Object[][] tmp = new Object[fields.length + 1][2];
        for (int i = 0; i < fields.length; i++)
            tmp[i] = fields[i];
        for (int i = fields.length; i < tmp.length; i++)
            tmp[i] = new Object[] {null, null};
        fields = tmp;
        // Recursive call with expanded fields:
        return makeField(id);
    }
    
    public Object getField(String id)
        throws NoSuchFieldException
    {
        return fields[getFieldNumber(id)][1];
    }
    
    public Object setField(String id, Object value)
        throws DynamicFieldsException
    {
        if (value == null)
        {
            // Most exceptions don't have a "cause" constructor.
            // In these cases you must use initCause(),
            // available in all Throwable subclasses.
            DynamicFieldsException dfe = new DynamicFieldsException();
            dfe.initCause(new NullPointerException());
            throw dfe;
        }
        int fieldNumber = hasField(id);
        if (fieldNumber == -1)
            fieldNumber = makeField(id);
        Object result = null;
        try
        {
            result = getField(id); // Get old value
        }
        catch (NoSuchFieldException e)
        {
            // Use constructor that takes "cause":
            throw new RuntimeException(e);
        }
        fields[fieldNumber][1] = value;
        return result;
    }
    
    public static void main(String[] args)
    {
        DynamicFields df = new DynamicFields(3);
        print(df);
        try
        {
            df.setField("d", "A value for d");
            df.setField("number", 47);
            df.setField("number2", 48);
            print(df);
            df.setField("d", "A new value for d");
            df.setField("number3", 11);
            print("df: " + df);
            print("df.getField(\"d\") : " + df.getField("d"));
            Object field = df.setField("d", null); // Exception
        }
        catch (NoSuchFieldException e)
        {
            e.printStackTrace(System.out);
        }
        catch (DynamicFieldsException e)
        {
            e.printStackTrace(System.out);
        }
    }
} /*
   * Output: null: null null: null null: null
   * 
   * d: A value for d number: 47 number2: 48
   * 
   * df: d: A new value for d number: 47 number2: 48 number3: 11
   * 
   * df.getField("d") : A new value for d DynamicFieldsException at DynamicFields.setField(DynamicFields.java:64) at
   * DynamicFields.main(DynamicFields.java:94) Caused by: java.lang.NullPointerException at
   * DynamicFields.setField(DynamicFields.java:66) ... 1 more
   */// :~
